#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>


#define GLOBALMEM_SIZE      0x1000
#define GLOBALMEM_MAGIC 'g'

#define MEM_CLEAR          _IO(GLOBALMEM_MAGIC,0)
#define GLOBALMEM_MAJOR     230
#define GLOBALMEM_NAME "globalmem"
static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major,int ,S_IRUGO);

struct globalmem_dev{
    struct cdev cdev;
    unsigned char mem[GLOBALMEM_SIZE];
};

struct globalmem_dev *globalmem_devp;


/**
 * seek 函数对于文件定位的起始位置是文件开头 SEEK_SET 0,当前位置SEEK_CUR 1 文件结尾 SEEK_2
 * 
 * globalmem_llseek 支持从文件开头和当前位置的相对偏移
 * 
 */
loff_t  globalmem_llseek            (struct file *filep, loff_t offset, int orig)
{
    loff_t ret = 0;
    switch(orig)
    {
        case 0://从文件开头位置seek
            if(offset < 0){
                return -EINVAL;
                break;
            }
            if((unsigned int)offset > GLOBALMEM_SIZE){
                return -EINVAL;
                break;
            }
            filep->f_pos =(unsigned int )offset;//从光标位置为偏移量
            ret = filep->f_pos;
        break;
        case 1://从文文件当前位置开始seek
            if((filep->f_pos+offset)>GLOBALMEM_SIZE){//当光标位置+设置的偏移量 大于mem大小,设置失败
               ret = -EINVAL;
               break;  
            }
            if((filep->f_pos + offset)<0){
                ret = -EINVAL;
                break;
            }
            filep->f_pos +=offset;//光标从当前位置向后移动offset
            ret = filep->f_pos;
            break;
        break;
        default:
            ret = -EINVAL;
        break;
    }
    return ret;
}
ssize_t globalmem_read              (struct file *filep, char __user * buf, size_t size, loff_t *ppos)
{
        unsigned long p =*ppos;
        unsigned int count = size;
        int ret;

        struct globalmem_dev *dev = filep->private_data;
        if(p>=GLOBALMEM_SIZE){//偏移量大于最大值
            return 0;
        }
        if(count > GLOBALMEM_SIZE-p)//要读出的数据，大于剩余的数据时
        {
            count = GLOBALMEM_SIZE-p;
        }
        //从mem中读出长度为count的数据到buf中
        if(copy_to_user(buf,dev->mem+p,count)){//读取失败
            ret = -EFAULT;
        }
        else{//读取成功
            *ppos +=count;//偏移量向后移动count
            ret =count ;//返回读取到的数据个数
            printk(KERN_INFO"raed %u bytes from %lu \n",count , p);
        }
        
        return ret;
}
ssize_t globalmem_write             (struct file *filep, const char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;

    int ret = 0;
    struct globalmem_dev *dev = filep->private_data;
    
    if(p>=GLOBALMEM_SIZE)//偏移量大于最大值，不能写入
    {
        return 0;
    }
    if(count>GLOBALMEM_SIZE - p){//内存不够写入count
        count = GLOBALMEM_SIZE - p;
    }
    /*开始写入*/
    if(copy_from_user(dev->mem,buf,count)){//写入失败
        ret = -EFAULT;
    }
    else{//写入完成
        *ppos +=count;//移动偏移量
        ret = count;//返回写入个数
        printk(KERN_INFO"written %d bytes from %lu \n",count ,p);
    }
    return ret;

}
int     globalmem_open              (struct inode *inode, struct file *filep)
{
        filep->private_data = globalmem_devp;
        return 0;
}
int     globalmem_release           (struct inode *inode, struct file *filep)
{
        return 0;
}
/**
 * ioctl 接收到MEM_CLEAR命令之后,这个命令将所有数据清零,其他命令返回 EINVAL
 * 
 *  命令码使用幻数
 * 
 */
long    globalmem_unlocked_ioctl    (struct file *filep, unsigned int cmd, unsigned long arg)
{
        struct globalmem_dev *dev = filep->private_data;
        switch(cmd){
            case MEM_CLEAR:
                memset(dev->mem,0,GLOBALMEM_SIZE);
                printk(KERN_INFO"globalmem is set all zero \n");
            break;
            default:
                return -EINVAL;
            break;
        }
        return 0;
}
static const struct file_operations globalmem_fops = {
    .owner              = THIS_MODULE,
	.llseek             = globalmem_llseek,
	.read               = globalmem_read,
	.write              = globalmem_write,
	.unlocked_ioctl     = globalmem_unlocked_ioctl,
	.open               = globalmem_open,
	.release            = globalmem_release,
};
static void globalmem_setup_cdev(struct globalmem_dev *dev,int index)
{
     int err,devno = MKDEV(globalmem_major,index);
     cdev_init(&dev->cdev,&globalmem_fops);
     dev->cdev.owner = THIS_MODULE;
     err = cdev_add(&dev->cdev,devno,1);
     if(err){
         printk(KERN_NOTICE"Error %d adding globalmem%d",err,index);
     }

}
static int __init globalmem_init(void)
{
    int ret;
    dev_t devno = MKDEV(globalmem_major,0);
    if(globalmem_major){
        ret = register_chrdev_region(devno,1,GLOBALMEM_NAME);
    }
    else{
        ret = alloc_chrdev_region(&devno,0,1,GLOBALMEM_NAME);
    }
    if(ret<0){
        return ret;
    }
    globalmem_devp = kzalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
    if(!globalmem_devp){
        ret = -ENOMEM;
        goto fail_malloc;
    }
    globalmem_setup_cdev(globalmem_devp,0);
    return 0;

    fail_malloc:
    unregister_chrdev_region(devno,1);
    return ret;
}
module_init(globalmem_init);

static void __exit globalmem_exit(void)
{
    cdev_del(&globalmem_devp->cdev);
    kfree(globalmem_devp);
    unregister_chrdev_region(MKDEV(globalmem_major,0),1);
}
module_exit(globalmem_exit);

MODULE_LICENSE("GPL v2");


